package com.starview.platform.data.type;

import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;

/**
 * An implementation of the {@link ITypeVisitor} that is used to rewrite type expressions, in
 * particular applying type aliases.
 * 
 * It is similar to type refreshing, except that it is not type variables but type expressions that
 * are renamed.
 * 
 * @author fgm
 * 
 */

public class TypeSubstitute implements ITypeVisitor
{
  private final Map<IType, IType> bound;
  private final Stack<IType> exclusions;
  protected final Stack<IType> stack;

  private TypeSubstitute(Map<IType, IType> bound, Stack<IType> exclusions, Stack<IType> stack)
  {
    this.bound = bound;
    this.stack = stack;
    this.exclusions = exclusions;
  }

  /**
   * Refresh a type while updating a supplied map with the variables that were found and their
   * replacements.
   * 
   * @param type
   *          the type to apply renaming to
   * @param bound
   *          a map which contains the renaming mapping
   * @return the type original expression with types substituted as determined by the mapping
   */
  public static IType rename(IType type, Map<IType, IType> bound)
  {
    Stack<IType> stack = new Stack<IType>();
    Stack<IType> exclusions = new Stack<IType>();
    TypeSubstitute renamer = new TypeSubstitute(bound, exclusions, stack);

    type.accept(renamer);
    assert stack.size() == 1;
    return stack.pop();
  }

  @Override
  public void visitSimpleType(Type t)
  {
    if (!exclusions.contains(t)) {
      IType renamed = bound.get(t);
      if (renamed != null)
        stack.push(renamed);
      else
        stack.push(t);
    } else
      stack.push(t);
  }

  @Override
  public void visitTypeExp(TypeExp t)
  {
    t.getTypeCon().accept(this);
    IType tyCon = stack.pop();

    IType typeArgs[] = t.getTypeArgs();

    for (int ix = 0; ix < typeArgs.length; ix++)
      typeArgs[ix].accept(this);
    assert stack.size() >= typeArgs.length;
    IType newArgs[] = new IType[typeArgs.length];
    for (int ix = typeArgs.length - 1; ix >= 0; ix--)
      newArgs[ix] = stack.pop();

    stack.push(TypeUtils.typeExp(tyCon, newArgs));
  }

  @Override
  public void visitTypeInterface(TypeInterfaceType t)
  {
    SortedMap<String, IType> nF = new TreeMap<String, IType>();
    SortedMap<String, IType> nT = new TreeMap<String, IType>();

    for (Entry<String, IType> entry : t.getAllFields().entrySet()) {
      entry.getValue().accept(this);
      nF.put(entry.getKey(), stack.pop());
    }
    for (Entry<String, IType> entry : t.getAllTypes().entrySet()) {
      entry.getValue().accept(this);
      nT.put(entry.getKey(), stack.pop());
    }

    stack.push(new TypeInterfaceType(nT, nF));
  }

  @Override
  public void visitTypeVar(TypeVar var)
  {
    stack.push(var);
  }

  @Override
  public void visitExistentialType(QuantifiedType t)
  {
    int mark = exclusions.size();
    TypeVar boundVar = t.getBoundVar();
    exclusions.push(boundVar);
    t.getBoundType().accept(this);
    IType renamed = stack.pop();
    stack.push(new ExistentialType(boundVar, renamed));
    exclusions.setSize(mark);
  }

  @Override
  public void visitUniversalType(UniversalType t)
  {
    int mark = exclusions.size();
    TypeVar boundVar = t.getBoundVar();
    exclusions.push(boundVar);
    t.getBoundType().accept(this);
    IType renamed = stack.pop();
    stack.push(new UniversalType(boundVar, renamed));
    exclusions.setSize(mark);
  }
}
